﻿using Abp.Settings;
using Castle.Core.Logging;
using Exceptionless;
using Exceptionless.Models;
using Exceptionless.Plugins;
using Exceptionless.Extensions;
using System;
using System.Collections.Generic;
using System.Text;

namespace Abp.Logging
{
    public class ExceptionlessLogger : ILogger
    //, Abp.Dependency.ITransientDependency
    {
        public ExceptionlessLogger()
        {
            this.LoggerLevel = LoggerLevel.Trace;
        }

        public ExceptionlessLogger(Type type) : this(type, LoggerLevel.Trace)
        {
        }
        public ExceptionlessLogger(Type type, LoggerLevel level) : this(type.Name, level)
        {
        }
        public ExceptionlessLogger(string name) : this(name, LoggerLevel.Trace)
        {
        }
        public ExceptionlessLogger(string name, LoggerLevel level) : this()
        {
            SourceName = name;
            LoggerLevel = level;
            ReadFromSetting();
        }

        void ReadFromSetting()
        {
            var client = ExceptionlessClient.Default;

            string value = client.Configuration.Settings.GetString(ExceptionlessSettingProvider.MinLogLevel, "Warn");
            LoggerLevel level = Enum.Parse<LoggerLevel>(value);
            if (this.LoggerLevel != LoggerLevel.Off && level != LoggerLevel.Off)
            {
                int minValue = Math.Min((int)this.LoggerLevel, (int)level);
                this.LoggerLevel = (LoggerLevel)minValue;
            }
            else
            {
                this.LoggerLevel = LoggerLevel.Off;
            }
        }


        protected string SourceName { get; }

        protected LoggerLevel LoggerLevel { get; private set; }
        protected ILogger Parent { get; private set; }
        #region ILogger

        public bool IsTraceEnabled => this.LoggerLevel == LoggerLevel.Trace;

        public bool IsDebugEnabled => this.IsTraceEnabled || this.LoggerLevel == LoggerLevel.Debug;

        public bool IsErrorEnabled => this.IsWarnEnabled || this.LoggerLevel == LoggerLevel.Error;

        public bool IsFatalEnabled => this.IsErrorEnabled || this.LoggerLevel == LoggerLevel.Fatal;

        public bool IsInfoEnabled => this.IsDebugEnabled || this.LoggerLevel == LoggerLevel.Info;

        public bool IsWarnEnabled => this.IsInfoEnabled || this.LoggerLevel == LoggerLevel.Warn;

        public ILogger CreateChildLogger(string loggerName)
        {
            var l = new ExceptionlessLogger(loggerName);
            l.Parent = this;
            return l;
        }

        public void Debug(string message)
        {
            if (this.IsDebugEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                var evt = client.CreateLog(SourceName, message, Exceptionless.Logging.LogLevel.Debug);
                evt.AddTags(nameof(Debug));
                evt.SetSource(SourceName);
                evt.Submit();
            }
        }

        public void Debug(Func<string> messageFactory)
        {
            if (this.IsDebugEnabled)
            {
                string message = null;
                try
                {
                    message = messageFactory();
                }
                catch (Exception exp)
                {
                    var client = Exceptionless.ExceptionlessClient.Default;
                    client.CreateException(exp).Submit();
                    return;
                }
                this.Debug(message);
            }
        }

        public void Debug(string message, Exception exception)
        {
            if (this.IsDebugEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                EventBuilder evt;
                if (exception != null)
                {
                    evt = FormatException(message, exception, client);
                }
                else
                {
                    evt = client.CreateLog(message, Exceptionless.Logging.LogLevel.Debug);
                }
                if (evt != null)
                {
                    evt.SetProperty(Event.KnownDataKeys.Level, Exceptionless.Logging.LogLevel.Debug);
                    evt.AddTags(nameof(Debug));
                    evt.SetSource(SourceName);
                    evt.Submit();
                }
            }
        }

        public void DebugFormat(string format, params object[] args)
        {
            if (this.IsDebugEnabled)
            {
                this.Debug(string.Format(format, args));
            }
        }

        public void DebugFormat(Exception exception, string format, params object[] args)
        {
            if (this.IsDebugEnabled)
            {
                this.Debug(string.Format(format, args), exception);
            }
        }

        public void DebugFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsDebugEnabled)
            {
                this.Debug(string.Format(format, args, formatProvider));
            }
        }

        public void DebugFormat(Exception exception, IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsDebugEnabled)
            {
                this.Debug(string.Format(format, args, formatProvider), exception);
            }
        }

        public void Error(string message)
        {
            if (this.IsErrorEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                EventBuilder evt;
                {
                    evt = client.CreateException(new Exception(message));
                    evt.SetProperty(Event.KnownDataKeys.Level, Exceptionless.Logging.LogLevel.Error);
                }
                evt.AddTags(nameof(Exception));
                evt.SetSource(SourceName);
                evt.Submit();
            }
        }

        public void Error(Func<string> messageFactory)
        {
            if (this.IsErrorEnabled)
            {
                string message = null;
                try
                {
                    message = messageFactory();
                }
                catch (Exception exp)
                {
                    var client = Exceptionless.ExceptionlessClient.Default;
                    client.CreateException(exp).Submit();
                    return;
                }
                this.Error(message);
            }
        }

        public void Error(string message, Exception exception)
        {
            if (this.IsErrorEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                EventBuilder evt;
                if (exception != null)
                {
                    evt = FormatException(message, exception, client);
                }
                else
                {
                    evt = client.CreateException(new Exception(message));

                }
                if (evt != null)
                {
                    evt.SetProperty(Event.KnownDataKeys.Level, Exceptionless.Logging.LogLevel.Error);
                    evt.AddTags(nameof(Exception));
                    evt.SetSource(this.SourceName);
                    evt.Submit();
                }
            }
        }

        public void ErrorFormat(string format, params object[] args)
        {
            if (this.IsErrorEnabled)
            {
                this.Error(string.Format(format, args));
            }
        }

        public void ErrorFormat(Exception exception, string format, params object[] args)
        {
            if (this.IsErrorEnabled)
            {
                this.Error(string.Format(format, args), exception);
            }
        }

        public void ErrorFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsErrorEnabled)
            {
                this.Error(string.Format(format, args, formatProvider));
            }
        }

        public void ErrorFormat(Exception exception, IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsErrorEnabled)
            {
                this.Error(string.Format(format, args, formatProvider), exception);
            }
        }

        public void Fatal(string message)
        {
            if (this.IsFatalEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                var evt = client.CreateException(new Exception(message));
                //.SetException(exception)
                evt.SetProperty(Event.KnownDataKeys.Level, Exceptionless.Logging.LogLevel.Fatal);
                evt.AddTags(nameof(Fatal), nameof(Exception));
                evt.SetSource(this.SourceName);
                evt.Submit();
            }
        }

        public void Fatal(Func<string> messageFactory)
        {
            if (this.IsFatalEnabled)
            {
                string message = null;
                try
                {
                    message = messageFactory();
                }
                catch (Exception exp)
                {
                    var client = Exceptionless.ExceptionlessClient.Default;
                    client.CreateException(exp).Submit();
                    return;
                }
                this.Fatal(message);
            }
        }

        public void Fatal(string message, Exception exception)
        {
            if (this.IsFatalEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                EventBuilder evt;
                if (exception != null)
                {
                    evt = FormatException(message, exception, client);
                }
                else
                {
                    evt = client.CreateLog(message, Exceptionless.Logging.LogLevel.Fatal);
                }
                if (evt != null)
                {
                    evt.SetProperty(Event.KnownDataKeys.Level, Exceptionless.Logging.LogLevel.Fatal);
                    evt.AddTags(nameof(Fatal), nameof(Exception));
                    evt.SetSource(this.SourceName);
                    evt.Submit();
                }

            }
        }

        public void FatalFormat(string format, params object[] args)
        {
            if (this.IsFatalEnabled)
            {
                this.Fatal(string.Format(format, args));
            }
        }

        public void FatalFormat(Exception exception, string format, params object[] args)
        {
            if (this.IsFatalEnabled)
            {
                this.Fatal(string.Format(format, args), exception);
            }
        }

        public void FatalFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsFatalEnabled)
            {
                this.Fatal(string.Format(format, args, formatProvider));
            }
        }

        public void FatalFormat(Exception exception, IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsFatalEnabled)
            {
                this.Fatal(string.Format(format, args, formatProvider), exception);
            }
        }

        public void Info(string message)
        {
            if (this.IsInfoEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                var evt = client.CreateLog(message, Exceptionless.Logging.LogLevel.Info);
                //.SetException(exception)
                evt.AddTags(nameof(Info));
                evt.SetSource(this.SourceName);
                evt.Submit();
            }
        }

        public void Info(Func<string> messageFactory)
        {
            if (this.IsInfoEnabled)
            {
                string message = null;
                try
                {
                    message = messageFactory();
                }
                catch (Exception exp)
                {
                    var client = Exceptionless.ExceptionlessClient.Default;
                    client.CreateException(exp).Submit();
                    return;
                }
                this.Info(message);
            }
        }

        public void Info(string message, Exception exception)
        {
            if (this.IsInfoEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                EventBuilder evt;
                if (exception != null)
                {
                    evt = FormatException(message, exception, client);
                }
                else
                {
                    evt = client.CreateLog(message, Exceptionless.Logging.LogLevel.Info);
                }
                if (evt != null)
                {
                    evt.SetProperty(Event.KnownDataKeys.Level, Exceptionless.Logging.LogLevel.Info);
                    evt.AddTags(nameof(Info));
                    evt.SetSource(this.SourceName);
                    evt.Submit();
                }
            }
        }

        public void InfoFormat(string format, params object[] args)
        {
            if (this.IsInfoEnabled)
            {
                this.Info(string.Format(format, args));
            }
        }

        public void InfoFormat(Exception exception, string format, params object[] args)
        {
            if (this.IsInfoEnabled)
            {
                this.Info(string.Format(format, args), exception);
            }
        }

        public void InfoFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsInfoEnabled)
            {
                this.Info(string.Format(format, args, formatProvider));
            }
        }

        public void InfoFormat(Exception exception, IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsInfoEnabled)
            {
                this.Info(string.Format(format, args, formatProvider), exception);
            }
        }

        public void Trace(string message)
        {
            if (this.IsTraceEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                var evt = client.CreateLog(message, Exceptionless.Logging.LogLevel.Trace);
                //.SetException(exception)
                evt.AddTags(nameof(Trace));
                evt.SetSource(this.SourceName);
                evt.Submit();
            }
        }

        public void Trace(Func<string> messageFactory)
        {
            if (this.IsTraceEnabled)
            {
                string message = null;
                try
                {
                    message = messageFactory();
                }
                catch (Exception exp)
                {
                    var client = Exceptionless.ExceptionlessClient.Default;
                    client.CreateException(exp).Submit();
                    return;
                }
                this.Trace(message);
            }
        }

        public void Trace(string message, Exception exception)
        {
            if (this.IsTraceEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                EventBuilder evt;
                if (exception != null)
                {
                    evt = FormatException(message, exception, client);
                }
                else
                {
                    evt = client.CreateLog(message, Exceptionless.Logging.LogLevel.Trace);
                }
                if (evt != null)
                {
                    evt.SetProperty(Event.KnownDataKeys.Level, Exceptionless.Logging.LogLevel.Trace);
                    evt.AddTags(nameof(Trace));
                    evt.SetSource(this.SourceName);
                    evt.Submit();
                }
            }
        }

        public void TraceFormat(string format, params object[] args)
        {
            if (this.IsTraceEnabled)
            {
                this.Trace(string.Format(format, args));
            }
        }

        public void TraceFormat(Exception exception, string format, params object[] args)
        {
            if (this.IsTraceEnabled)
            {
                this.Trace(string.Format(format, args), exception);
            }
        }

        public void TraceFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsTraceEnabled)
            {
                this.Trace(string.Format(format, args, formatProvider));
            }
        }

        public void TraceFormat(Exception exception, IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsTraceEnabled)
            {
                this.Trace(string.Format(format, args, formatProvider), exception);
            }
        }

        public void Warn(string message)
        {
            if (this.IsWarnEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                var evt = client.CreateLog(message, Exceptionless.Logging.LogLevel.Warn);
                //.SetException(exception)
                evt.AddTags(nameof(Warn));
                evt.SetSource(this.SourceName);
                evt.Submit();
            }
        }

        public void Warn(Func<string> messageFactory)
        {
            if (this.IsWarnEnabled)
            {
                string message = null;
                try
                {
                    message = messageFactory();
                }
                catch (Exception exp)
                {
                    var client = Exceptionless.ExceptionlessClient.Default;
                    client.CreateException(exp).Submit();
                    return;
                }
                this.Warn(message);
            }
        }

        public void Warn(string message, Exception exception)
        {
            if (this.IsWarnEnabled)
            {
                var client = Exceptionless.ExceptionlessClient.Default;
                EventBuilder evt;
                if (exception != null)
                {
                    evt = FormatException(message, exception, client);
                }
                else
                {
                    evt = client.CreateLog(message, Exceptionless.Logging.LogLevel.Warn);
                }
                if (evt != null)
                {
                    evt.SetProperty(Event.KnownDataKeys.Level, Exceptionless.Logging.LogLevel.Warn);
                    evt.AddTags(nameof(Warn));
                    evt.SetSource(this.SourceName);
                    evt.Submit();
                }
            }
        }


        public void WarnFormat(string format, params object[] args)
        {
            if (this.IsWarnEnabled)
            {
                this.Warn(string.Format(format, args));
            }
        }

        public void WarnFormat(Exception exception, string format, params object[] args)
        {
            if (this.IsWarnEnabled)
            {
                this.Warn(string.Format(format, args), exception);
            }
        }

        public void WarnFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsWarnEnabled)
            {
                this.Warn(string.Format(format, args, formatProvider));
            }
        }

        public void WarnFormat(Exception exception, IFormatProvider formatProvider, string format, params object[] args)
        {
            if (this.IsWarnEnabled)
            {
                this.Warn(string.Format(format, args, formatProvider), exception);
            }
        }

        #endregion



        private EventBuilder FormatException(string message, Exception exception, ExceptionlessClient client)
        {
            switch (this.SourceName)
            {
                case "Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware":
                case "AbpExceptionFilter":
                    return null;
            }
            EventBuilder evt = client.CreateException(exception);
            evt.SetMessage(message);
            evt.AddTags(nameof(Exception), exception.GetType().Name);
            return evt;
        }
    }
}
